package com.bckj.fastboot.core.extra.txasync;

import com.bckj.fastboot.core.extra.txasync.constant.TxAsyncConstants;
import com.bckj.fastboot.core.jackson.JsonUtils;
import com.bckj.fastboot.core.lang.exception.BusinessException;
import com.bckj.fastboot.core.extra.txasync.constant.TxAsyncEnum;
import com.bckj.fastboot.core.extra.txasync.entity.AsyncInvocation;
import com.bckj.fastboot.core.extra.txasync.repo.AsyncInvocationRepository;
import com.bckj.fastboot.core.util.SpringUtils;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.support.RetryTemplateBuilder;
import org.springframework.transaction.support.TransactionTemplate;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

@Slf4j
@RequiredArgsConstructor
public class TxAsyncRunner {

    private final AsyncInvocationRepository asyncInvocationRepository;
    private final TxAsyncVirtualThreadFactory virtualThreadFactory;
    @Resource
    private TransactionTemplate transactionTemplate;


    public void execute(AsyncInvocation asyncInvocation, Runnable runnable) {
        AsyncInvocation upd = new AsyncInvocation();
        upd.setId(asyncInvocation.getId());
        upd.setVersion(asyncInvocation.getVersion());
        upd.setRetryCount(asyncInvocation.getRetryCount());
        virtualThreadFactory.newThread(() -> {
            RetryTemplate retryTemplate = buildRetry(asyncInvocation);
            retryTemplate.execute(context -> {
                transactionTemplate.execute(status -> {
                    try {
                        if (Objects.equals(asyncInvocation.getStatus(), TxAsyncEnum.READY.getCode())) {
                            upd.setStatus(TxAsyncEnum.RUNNING.getCode());
                        }
                        runnable.run();
                        upd.setStatus(TxAsyncEnum.SUCCESS.getCode());
                    } catch (Exception e) {
                        log.error("异步任务执行异常", e);
                        if (upd.getRetryCount() >= asyncInvocation.getMaxAttempt()) {
                            upd.setStatus(TxAsyncEnum.FAIL.getCode());
                        } else {
                            upd.setStatus(TxAsyncEnum.EXCEPTION.getCode());
                        }
                        throw e;
                    }
                    return null;
                });
                return null;
            }, retryContext -> {
                upd.setRetryCount(asyncInvocation.getRetryCount() + retryContext.getRetryCount());
                return null;
            });
            boolean r = asyncInvocationRepository.updateById(upd);
            if (!r) {
                log.error("异步任务更新失败 taskId=" + asyncInvocation.getTaskId());
            }
        }).start();
    }

    public void retry(String taskId) {
        AsyncInvocation asyncInvocation = asyncInvocationRepository.getByTaskId(taskId);
        if (asyncInvocation == null) {
            throw new RuntimeException("任务执行失败，未找到任务");
        }
        if (!Objects.equals(asyncInvocation.getStatus(), TxAsyncEnum.FAIL.getCode())) {
            throw new RuntimeException("任务执行失败，任务状态不正确");
        }
        Object bean = SpringUtils.getBean(asyncInvocation.getClassName());

        Method method = ReflectionUtils.getMethod(bean.getClass(), asyncInvocation.getMethodName()).orElseThrow();
        Class<?> parameterType = method.getParameterTypes()[0];
        Object params = JsonUtils.toBean(asyncInvocation.getParams(), parameterType);
        AsyncInvocation upd = new AsyncInvocation();
        upd.setId(asyncInvocation.getId());
        upd.setRetryCount(asyncInvocation.getRetryCount() + 1);
        try {
            transactionTemplate.execute(status -> {
                if (params instanceof TxAsyncParam tap) {
                    tap.setRetryFlag(TxAsyncConstants.RETRY_FLAG_RETRYING);
                    try {
                        method.invoke(bean, tap);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        throw new BusinessException(e.getMessage());
                    }
                    upd.setStatus(TxAsyncEnum.SUCCESS.getCode());
                } else {
                    throw new RuntimeException("任务执行失败，任务参数类型不正确");
                }
                return null;
            });
        } catch (Exception e) {
            log.error("异步任务执行异常" + e.getMessage(), e);
        }
        asyncInvocationRepository.updateById(upd);
    }

    private RetryTemplate buildRetry(AsyncInvocation asyncInvocation) {
        RetryTemplateBuilder builder = new RetryTemplateBuilder()
                .maxAttempts(asyncInvocation.getMaxAttempt());
        if (asyncInvocation.getMultiplier() == 0D) {
            builder.fixedBackoff(asyncInvocation.getInitialInterval());
        } else {
            builder.exponentialBackoff(asyncInvocation.getInitialInterval(), asyncInvocation.getMultiplier(), asyncInvocation.getTimeout());
        }
        return builder.build();
    }
}
